/**
 * Grammar.cs
 * 
 * AUTHOR:
 * Zlatko Michailov (http://blogs.msdn.com/esql)
 * 
 * PURPOSE:
 * Transalation of a non-recursive grammar to a single regex pattern.
 * Each non-terminal symbol is defined as a regex group.
 * The first definition is assumed to be the root one.
 * 
 * COPYRIGHT:
 * (c) Microsoft Corp., 2006-2007
 */



using System;
using System.Text;
using System.Text.RegularExpressions;



namespace Microsoft.Samples.Data.eSqlBlast
{
    public sealed class Grammar
    {
        private const string _ReferenceGroupName = @"NonTerminal"; 
        private const string _ReferencePattern   = @"\\K\<(?<" + _ReferenceGroupName + @">\w+)\>";  // \K<NonTerminal>
        private const string _DefinitionFormat   = @"(?<{0}>";                                      // (?<NonTerminal> ...


        private enum ProcessingStatus
        {
            NotStarted,
            InProgress,
            Complete,
        }


        private class ProcessedDefinition
        {
            public string Definition;
            public ProcessingStatus Status;
            public string Pattern;

            public ProcessedDefinition(string definition)
            {
                Definition = definition;
                Status = ProcessingStatus.NotStarted;
                Pattern = null;
            }
        }

        // Data memebers
        private ProcessedDefinition[] _Definitions;


        public Grammar(string[] definitions)
        {
            _Definitions = new ProcessedDefinition[definitions.Length];
            for (int i = 0; i < definitions.Length; i++)
            {
                _Definitions[i] = new ProcessedDefinition(definitions[i]);
            }
        }


        public string Pattern
        {
            get
            {
                return GetPatternOf(0);
            }
        }


        public string GetPatternOf(int i)
        {
            if (_Definitions[i].Status != ProcessingStatus.Complete)
            {
                ComputePattern(i);
            }

            return _Definitions[i].Pattern;
        }


        private void ComputePattern(int iThis)
        {
            _Definitions[iThis].Status = ProcessingStatus.InProgress;

            // Check if the current definition references other non-terminals - '\K<NonTerminal>'.
            StringBuilder pattern = new StringBuilder();
            Regex referencePattern = new Regex(_ReferencePattern);
            MatchCollection references = referencePattern.Matches(_Definitions[iThis].Definition);
            int position = 0;

            // For each referenced non-terminal.
            foreach (Match reference in references)
            {
                // Find the referenced definition.
                string nonTerminalName = reference.Groups[_ReferenceGroupName].Value;
                int i = FindDefinition(nonTerminalName);

                // If the referenced definition is not yet computed, 
                // compute it, and keep the regex pattern.
                if (_Definitions[i].Status == ProcessingStatus.NotStarted)
                {
                    ComputePattern(i);
                }

                // By this time the referenced definition must be computed.
                if (_Definitions[i].Status == ProcessingStatus.Complete)
                {
                    string nonTerminalPattern = _Definitions[i].Pattern;

                    // Replace '\K<NonTerminal>' with the computed pattern of NonTerminal.
                    pattern.Append(_Definitions[iThis].Definition.Substring(position, reference.Index - position));
                    position = reference.Index + reference.Value.Length;
                    pattern.Append(nonTerminalPattern);
                }
                else if (_Definitions[i].Status == ProcessingStatus.InProgress)
                {
                    // If we are referencing a non-terminal that's already in progress,
                    // then we have a circular dependency. Throw.
                    throw new ArgumentException(string.Format("Circular dependency on '{0}' in '{1}'.", 
                                                nonTerminalName, _Definitions[iThis].Definition));
                }
            }
            // The text after the last reference has not been copied yet. Do it now.
            pattern.Append(_Definitions[iThis].Definition.Substring(position));

            // Save the computed pattern and mark the status.
            _Definitions[iThis].Pattern = pattern.ToString();
            _Definitions[iThis].Status = ProcessingStatus.Complete;
        }


        private int FindDefinition(string name)
        {
            // Build a definition head to search by.
            StringBuilder definitionBuilder = new StringBuilder();
            definitionBuilder.AppendFormat(_DefinitionFormat, name);
            string definitionHead = definitionBuilder.ToString();

            // Scan definitions sequentially and compare with the given definition head.
            for (int i = 0; i < _Definitions.Length; i++)
            {
                if (_Definitions[i].Definition.StartsWith(definitionHead))
                {
                    return i;
                }
            }

            throw new ArgumentException(string.Format("Missing definition of '{0}'.", name));
        }

    }
}
